package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.Collection;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.DefinePreset;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.FieldIR;
import mirrormonkey.util.annotations.parsing.MemberIR;
import mirrormonkey.util.annotations.parsing.memberlevel.FlatMemberIRManagementTest.LegalMemberIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning the annotation of membersById with and parsing of
 * preset annotations.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class PresetMemberTest {

	public static AnnotationParser parser;

	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
		parser.parsePresetClass(ContainingPresets.class);
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation2 {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface PresetAnnotation {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface PresetAnnotation2 {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface BothPresetAnnotations {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface MultiLevelPreset {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface IRDefiningPreset {
	}

	public static class ContainingPresets {

		@CustomAnnotation
		@DefinePreset(PresetAnnotation.class)
		public Object declarePreset;

		@CustomAnnotation2
		@DefinePreset(PresetAnnotation2.class)
		public Object declareAnotherPreset;

		@CustomAnnotation
		@CustomAnnotation2
		@DefinePreset(BothPresetAnnotations.class)
		public Object declareBothPresets;

		@PresetAnnotation
		@DefinePreset(MultiLevelPreset.class)
		public Object declareMultiLevelPreset;

		@CustomAnnotation
		@DefinePreset(IRDefiningPreset.class)
		@IRClass(value = LegalMemberIR.class, startCollect = { CustomAnnotation.class }, stopCollect = { CustomAnnotation2.class })
		public Object declareIRPreset;

	}

	public static int countMembers(ClassIR cir) {
		int collectedIRs = 0;
		for (Collection<MemberIR> i : cir.memberIRs.values()) {
			collectedIRs += i.size();
		}
		return collectedIRs;
	}

	public static void checkContainsMembers(ClassIR cir,
			Class<?>[][] shouldHaveBeenCollected) {
		checkContainsMembers(cir, shouldHaveBeenCollected,
				shouldHaveBeenCollected.length);
	}

	public static void checkContainsMembers(ClassIR cir,
			Class<?>[][] shouldHaveBeenCollected, int memberCount) {
		Assert.assertEquals(cir.memberIRs.toString(), memberCount,
				countMembers(cir));
		for (Class<?>[] cs : shouldHaveBeenCollected) {
			boolean found = false;
			for (Collection<MemberIR> mirs : cir.memberIRs.values()) {
				FindMemberIR: for (MemberIR mir : mirs) {
					if (mir.collectedAnnotations.size() != cs.length) {
						continue FindMemberIR;
					}
					for (Class<?> expectedClass : cs) {
						if (!mir.collectedAnnotations
								.containsKey(expectedClass)) {
							continue FindMemberIR;
						}
					}
					found = true;
				}
			}
			if (!found) {
				String msg = "\nmemberIRs: " + cir.memberIRs + "\n";
				Assert.fail(msg);
			}
		}
	}

	public static class ContainingPresetMember {

		@PresetAnnotation
		@IRClass(value = FieldIR.class, startCollect = { CustomAnnotation.class })
		public Object annotatedWithPresets;

	}

	/**
	 * Tests if a single preset on a member will be collected.
	 */
	@Test
	public void testPresetParse() {
		ClassIR ir = parser.parseClass(ContainingPresetMember.class, null);
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } });
	}

	public static class ContainingNoPresetMember {

		@IRClass(value = FieldIR.class, startCollect = { CustomAnnotation.class })
		public Object collectedButNotAnnotated;

	}

	/**
	 * Tests if the preset is not parsed if it is not requested.
	 */
	@Test
	public void testNoDeclareRejectPreset() {
		ClassIR ir = parser.parseClass(ContainingNoPresetMember.class, null);
		checkContainsMembers(ir, new Class<?>[][] { {} });
	}

	public static class ContainingNonCollectedPresetMember {

		@PresetAnnotation
		public Object annotatedButNotCollected;

	}

	/**
	 * Tests if the preset is parsed but annotations that should not be
	 * collected are not.
	 */
	@Test
	public void testNoCollectRejectPreset() {
		ClassIR ir = parser.parseClass(
				ContainingNonCollectedPresetMember.class, null);
		checkContainsMembers(ir, new Class<?>[][] {});
	}

	public static class ContainingMultiPresetMember {

		@PresetAnnotation
		@PresetAnnotation2
		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object annotatedTwoTimes;

	}

	/**
	 * Tests if multiple preset annotations can be used on a single member.
	 */
	@Test
	public void testMultiPresets() {
		ClassIR ir = parser.parseClass(ContainingMultiPresetMember.class, null);
		checkContainsMembers(ir, new Class<?>[][] { { CustomAnnotation.class,
				CustomAnnotation2.class } });
	}

	public static class ContainingPresetMultiMember {

		@BothPresetAnnotations
		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object shouldBeAnnotatedWithBothByPreset;

	}

	/**
	 * Tests if multiple annotations can be defined for a single preset.
	 */
	@Test
	public void testMultiPerPreset() {
		ClassIR ir = parser.parseClass(ContainingPresetMultiMember.class, null);
		checkContainsMembers(ir, new Class<?>[][] { { CustomAnnotation.class,
				CustomAnnotation2.class } });
	}

	public static class ContainingSelfCollectingPreset {
		@PresetAnnotation
		@IRClass(value = FieldIR.class, startCollect = { PresetAnnotation.class })
		public Object shouldCollectPresetAnnotation;
	}

	/**
	 * Tests if preset annotations themselves can be collected.
	 */
	@Test
	public void testCollectPreset() {
		ClassIR ir = parser.parseClass(ContainingSelfCollectingPreset.class,
				null);
		checkContainsMembers(ir,
				new Class<?>[][] { { PresetAnnotation.class } });
	}

	public static class ContainingMultiLevelPreset {

		@MultiLevelPreset
		@IRClass(value = FieldIR.class, startCollect = { CustomAnnotation.class })
		public Object shouldCollectCustomAnnotation;

	}

	/**
	 * Tests if it is possible to use preset annotations in preset definitions.
	 */
	@Test
	public void testMultiLevelPreset() {
		ClassIR ir = parser.parseClass(ContainingMultiLevelPreset.class, null);
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } });
	}

	public @interface SomeArbitraryPreset {
	}

	public static class AnotherPresetDefinitionClass {

		@CustomAnnotation2
		@DefinePreset(SomeArbitraryPreset.class)
		public Object defineArbitraryPreset;

	}

	public static class ContainingOverriddenPresetMember {

		@PresetAnnotation
		@IRClass(value = FieldIR.class, startCollect = CustomAnnotation2.class)
		public Object field;

	}

	/**
	 * Tests if it is possible to parse a second preset definition without
	 * losing the data from the first one.
	 */
	@Test
	public void testMultiDefinitionClasses() {
		parser.parsePresetClass(AnotherPresetDefinitionClass.class);
		ClassIR ir = parser.parseClass(ContainingPresetMember.class, null);
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } });
		parser.parsePresetClass(ContainingPresets.class);
	}

	public static class PresetDefinitionClassOverridingPresetAnnotation {

		@CustomAnnotation2
		@DefinePreset(PresetAnnotation.class)
		public Object overridePresetAnnotation;

	}

	/**
	 * Tests if it is possible to override presets when parsing annotations
	 * containing a preset for the same preset annotation.
	 */
	@Test
	public void testMultiDefinitionClassOverride() {
		parser.parsePresetClass(PresetDefinitionClassOverridingPresetAnnotation.class);
		ClassIR ir = parser.parseClass(ContainingOverriddenPresetMember.class,
				null);
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation2.class } });
		parser.parsePresetClass(ContainingPresets.class);
	}

	public static class ContainsPresetInitMember {

		@IRDefiningPreset
		public Object defineIRThroughPreset;

	}

	/**
	 * Tests if it is possible to use <tt>IRClass</tt> in preset definitions to
	 * initialize a <tt>ClassIR</tt>.
	 */
	@Test
	public void testPresetInitIRClass() {
		ClassIR ir = parser.parseClass(ContainsPresetInitMember.class, null);
		Assert.assertEquals(LegalMemberIR.class, ir.memberIRs.get(Field.class)
				.iterator().next().getClass());
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } }, 3);
	}

	public static class ContainsRegularMember {
		@IRClass(startCollect = { CustomAnnotation2.class })
		public Object field;
	}

	public static class OverridesRegularMember {
		@BothPresetAnnotations
		@IRDefiningPreset
		public Object field;
	}

	/**
	 * Tests if it is possible to use <tt>IRClass</tt> in preset definitions to
	 * change a <tt>ClassIR</tt>.
	 */
	@Test
	public void testPresetOverrideIRClass() {
		ClassIR ir = parser.parseClass(OverridesRegularMember.class, null);
		checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } }, 3);
	}

}
